package com.hzw.saas.service.old.storage.service;

import java.util.*;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hzw.saas.api.old.storage.IResourceFileService;
import com.hzw.saas.api.old.storage.IResourceFolderService;
import com.hzw.saas.api.old.storage.bo.ResourceFileBO;
import com.hzw.saas.api.old.storage.bo.ResourceFolderBO;
import com.hzw.saas.api.old.storage.bo.ResourceFolderConflictBO;
import com.hzw.saas.api.old.storage.constants.StorageConstants;
import com.hzw.saas.api.old.storage.enums.ConflictModeEnum;
import com.hzw.saas.api.old.storage.enums.FileIntegrityEnum;
import com.hzw.saas.api.old.storage.enums.ResourceStatusEnum;
import com.hzw.saas.api.old.storage.exception.FolderConflictException;
import com.hzw.saas.common.config.exception.DefaultSaasException;
import com.hzw.saas.common.config.util.AssertUtil;
import com.hzw.saas.common.security.utils.SecurityUtils;
import com.hzw.saas.common.util.IdWorker;
import com.hzw.saas.common.util.MapperUtil;
import com.hzw.saas.common.util.constants.PathConsts;
import com.hzw.saas.service.old.storage.mapper.OldResourceFolderMapper;
import com.hzw.saas.service.old.storage.model.ResourceFolder;
import com.hzw.saas.service.old.storage.util.StorageUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.text.StrSplitter;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.impl.DefaultMapperFactory;


/**
 * 文件目录默认实现
 * <p>
 * 目录存储约束规则:
 * 1. 同一父目录下不可存在多个同名子目录（正常状态）
 * 2. 同一父目录下可能存在多个同名子目录（回收或删除状态）
 * 3. 恢复被回收目录时，若上级目录被删除，则提示恢复目录至指定父目录
 * 4. 恢复被回收目录时，若恢复至父目录有同名目录时，则需要更改目录名
 * 5. 被删除资源无法更新状态至正常或回收
 *
 * @author zzl
 * @since 02/25/2021
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class OldResourceFolderServiceImpl extends ServiceImpl<OldResourceFolderMapper, ResourceFolder> implements IResourceFolderService, IService<ResourceFolder> {

    private final IdWorker idWorker;
    private final MapperFacade mapperFacade = new DefaultMapperFactory.Builder().mapNulls(false).build().getMapperFacade();
    private final IResourceFileService fileService;

    @Override
    public String getPathByPidPath(String pidPath) {
        List<String> ids = StrSplitter.splitByRegex(pidPath, PathConsts.ALL_SLASH_PATTERN, -1, true, true);
        return this.getPathByPidPath(ids);
    }

    @Override
    public String getPathByPidPath(List<String> pidPath) {
        StringBuilder result = new StringBuilder("");
        for (int i = 0; i < pidPath.size(); i++) {
            String pid = pidPath.get(i);
            ResourceFolder folderDO = super.lambdaQuery()
                .eq(ResourceFolder::getFolderId, pid)
                .eq(ResourceFolder::getStatus, ResourceStatusEnum.NORMAL.code())
                .one();
            if (Objects.isNull(folderDO))
                return null;
            if ((i > 0) && !Objects.equals(folderDO.getParentId(), pidPath.get(i - 1)))
                return null;
            result.append(PathConsts.SLASH).append(folderDO.getName());
        }
        return result.toString();
    }

    @Override
    public boolean isNameExistsInRoot(String ownerId, String catalog, String folderName) {
        String parentId = StorageConstants.ROOT_FOLDER_ID;
        return isNameExists(ownerId, catalog, parentId, folderName, ResourceStatusEnum.NORMAL.code());
    }

    @Override
    public boolean isNameExistsInFolder(String parentId, String folderName) {
        StorageUtil.isRootFolder(parentId, true);
        return isNameExists(null, null, parentId, folderName, ResourceStatusEnum.NORMAL.code());
    }

    @Override
    public boolean isPathExistsInRoot(String ownerId, String catalog, String path) {
        List<String> pathList = StorageUtil.resolvePath(path);
        boolean flag = true;
        String parentId = StorageConstants.ROOT_FOLDER_ID;
        for (String folderName : pathList) {
            ResourceFolderBO folderBO = this.getNormalFolderByName(ownerId, catalog, parentId, folderName, false);
            if (folderBO == null) {
                flag = false;
                break;
            }
            parentId = folderBO.getFolderId();
        }
        return flag;
    }

    @Override
    public boolean isPathExistsInFolder(String parentId, String path) {
        List<String> pathList = StorageUtil.resolvePath(path);
        boolean flag = true;
        for (String folderName : pathList) {
            ResourceFolderBO folderBO = this.getFolderByName(parentId, folderName, false);
            if (folderBO == null) {
                flag = false;
                break;
            }
            parentId = folderBO.getFolderId();
        }
        return flag;
    }

    @Override
    public ResourceFolderBO getFolder(String folderId, boolean throwNotFound) {
        StorageUtil.isRootFolder(folderId, true);
        ResourceFolder folderDO = this.getById(folderId);
        AssertUtil.assertThrow(String.format("目录(%s)不存在", folderId), throwNotFound && folderDO == null);
        return mapperFacade.map(folderDO, ResourceFolderBO.class);
    }

    @Override
    public ResourceFolderBO getFolder(String folderId, Integer status, boolean checkExists) {
        ResourceFolderBO folderBO = this.getFolder(folderId, checkExists);
        if (folderBO == null)
            return null;
        if (checkExists && status != null && ObjectUtil.notEqual(folderBO.getStatus(), status)) {
            ResourceStatusEnum resourceStatusEnum = ResourceStatusEnum.instance(folderBO.getStatus(), false);
            String statusText = resourceStatusEnum == null ? "" : resourceStatusEnum.text();
            throw DefaultSaasException.build(String.format("目录(%s)状态异常：%s", folderBO, statusText)).status(HttpStatus.BAD_REQUEST);
        }
        return folderBO;
    }

    @Override
    public ResourceFolderBO getFolder(String folderId, int pathLevel, boolean widthFile, Integer status, Integer fileIntegrity, boolean throwNotFound) {
        StorageUtil.isRootFolder(folderId, true);
        ResourceFolderBO folderBO = this.getFolder(folderId, status, throwNotFound);
        this.loopGetChildren(folderBO, pathLevel, widthFile, status, fileIntegrity);
        return folderBO;
    }

    @Override
    public ResourceFolderBO getRootFolder(String ownerId, String catalog, int pathLevel, boolean widthFile, Integer status, Integer fileIntegrity) {
        ResourceFolderBO rootFolderBO = new ResourceFolderBO()
            .setFolderId(StorageConstants.ROOT_FOLDER_ID)
            .setOwnerId(ownerId)
            .setCatalog(catalog)
            .setStatus(ResourceStatusEnum.NORMAL.code())
            .setName(StorageConstants.ROOT_FOLDER_NAME);
        this.loopGetChildren(rootFolderBO, pathLevel, widthFile, status, fileIntegrity);
        return rootFolderBO;
    }

    @Override
    public ResourceFolderBO getArchiveFolder(String ownerId, String catalog) {
        Integer status = ResourceStatusEnum.ARCHIVED.code();
        List<ResourceFolderBO> folderBOList = this.listFolderOnStatus(ownerId, catalog, status);
        for (ResourceFolderBO folderBO : folderBOList) {
            this.loopGetChildren(folderBO, -1, true, status, null);
        }
        List<ResourceFileBO> fileBOList = fileService.listFileInfo(ownerId, catalog, ResourceStatusEnum.ARCHIVED.code(), null);
        ResourceFolderBO archiveFolder = new ResourceFolderBO()
            .setName("回收站")
            .setOwnerId(ownerId)
            .setCatalog(catalog)
            .setSubFolders(folderBOList)
            .setSubFiles(fileBOList);
        return archiveFolder;
    }

    @Override
    public ResourceFolderBO getFolderByNameInRoot(String ownerId, String catalog, String name, boolean throwNotFound) {
        String parentId = StorageConstants.ROOT_FOLDER_ID;
        return this.getNormalFolderByName(ownerId, catalog, parentId, name, throwNotFound);
    }

    @Override
    public ResourceFolderBO getFolderByName(String parentId, String name, boolean throwNotFound) {
        StorageUtil.isRootFolder(parentId, true);
        return this.getNormalFolderByName(null, null, parentId, name, throwNotFound);
    }

    @Override
    public List<ResourceFolderBO> listFolders(List<String> folderIds, Integer status) {
        if (CollectionUtil.isEmpty(folderIds))
            return new ArrayList<>();
        folderIds.removeIf(StringUtils::isBlank);
        List<ResourceFolder> list = super.lambdaQuery().in(ResourceFolder::getFolderId, folderIds)
            .eq(Objects.nonNull(status), ResourceFolder::getStatus, status)
            .list();
        return MapperUtil.nf().mapAsList(list, ResourceFolderBO.class);
    }

    @Override
    public String getFullPath(String folderId, boolean throwNotFound) {
        if (StorageUtil.isRootFolder(folderId, false))
            return "";
        ResourceFolderBO folder = this.getFolder(folderId, true);
        String parentPath = this.getFullPath(folder.getParentId(), true);
        return parentPath + "/" + folder.getName();
    }

    @Override
    public List<ResourceFolderBO> listFolderOnStatus(String ownerId, String catalog, Integer status) {
        ResourceStatusEnum.instance(status, true);
        List<ResourceFolder> list = this.lambdaQuery()
            .eq(ResourceFolder::getOwnerId, ownerId)
            .eq(ResourceFolder::getCatalog, catalog)
            .eq(ResourceFolder::getStatus, status)
            .orderByDesc(ResourceFolder::getUpdateDate)
            .list();
        return mapperFacade.mapAsList(list, ResourceFolderBO.class);
    }

    @Override
    public long countFolderSize(String folderId) {
        long totalSize = 0L;
        if (StorageUtil.isRootFolder(folderId, false)) {
            return totalSize;
        }
        ResourceFolderBO folder = this.getFolder(folderId, -1, true, ResourceStatusEnum.NORMAL.code(), FileIntegrityEnum.NORMAL.code(), false);
        if (Objects.isNull(folder))
            return totalSize;
        if (CollectionUtil.isNotEmpty(folder.getSubFiles())) {
            totalSize = folder.getSubFiles().stream().mapToLong(ResourceFileBO::getLength).sum();
        }
        if (CollectionUtil.isNotEmpty(folder.getSubFolders())) {
            totalSize += folder.getSubFolders().stream().mapToLong(subFolder -> this.countFolderSize(subFolder.getFolderId())).sum();
        }
        return totalSize;
    }

    /**
     * 目录创建校验：
     * 1. 必要参数：父目录，目录名，所有者
     * 2. 父目录存在且状态正常
     * 3. 父目录下无正常状态的同名文件
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResourceFolderBO createFolder(ResourceFolderBO folderBO, ConflictModeEnum conflictHandleEnum) {
        this.checkCreateParam(folderBO);
        ResourceFolderBO folderExist = this.verifyFolderName(folderBO);
        if (folderExist != null) {
            switch (conflictHandleEnum) {
                case THROW_EXISTS:
                    throw FolderConflictException.build("存在同名目录", new ResourceFolderConflictBO(folderBO, folderExist));
                case MERGE_EXISTS:
                case RETURN_EXISTS:
                    log.debug("待创建目录已存在，返回该目录：" + JSONUtil.toJsonStr(folderExist));
                    return folderExist;
                case COVER_EXISTS:
                    this.updateFolderStatus(folderExist.getFolderId(), ResourceStatusEnum.DELETED_COVER.code(), false);
                    break;
                case RENAME_ORIGINAL:
                    String rename = this.getNextCopyName(folderExist.getOwnerId(), folderExist.getCatalog(), folderExist.getParentId(), folderExist.getName());
                    log.debug("待创建目录已存在，重命名目录：" + rename);
                    folderBO.setName(rename);
                    break;
            }
        }
        ResourceFolder folderDO = mapperFacade.map(folderBO, ResourceFolder.class);
        this.save(folderDO);
        log.debug("创建目录完成：" + JSONUtil.toJsonStr(folderBO));
        return folderBO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<ResourceFolderBO> createFoldersByPath(String ownerId, String creatorId, String catalog, String parentId, String path) {
        List<String> pathList = StorageUtil.resolvePath(path);
        List<ResourceFolderBO> folderBoList = new ArrayList<>();
        for (String folderName : pathList) {
            ResourceFolderBO folderBO = new ResourceFolderBO()
                .setOwnerId(ownerId)
                .setCreatorId(creatorId)
                .setCatalog(catalog)
                .setParentId(parentId)
                .setName(folderName);
            folderBO = this.createFolder(folderBO, ConflictModeEnum.RETURN_EXISTS);
            folderBoList.add(folderBO);
            parentId = folderBO.getFolderId();
        }
        return folderBoList;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void copyFoldersInfo(List<String> foldersId, String targetParentId, ConflictModeEnum conflictHandleEnum) {
        // 校验
        if (CollUtil.isEmpty(foldersId))
            return;
        // 去重
        foldersId = new ArrayList<>(new HashSet<>(foldersId));
        for (String folderId : foldersId) {
            this.copyFolderInfo(folderId, targetParentId, conflictHandleEnum);
        }
    }

    private void copyFolderInfo(String folderId, String targetParentId, ConflictModeEnum conflictHandleEnum) {
        String userId = SecurityUtils.getUser().getUserId();

        ResourceFolderBO originalFolder = this.getFolder(folderId, ResourceStatusEnum.NORMAL.code(), true);
        String originalOwnerId = originalFolder.getOwnerId();
        String originalCatalog = originalFolder.getCatalog();

        originalFolder.setFolderId(null);
        originalFolder.setParentId(targetParentId);
        originalFolder.setOwnerId(userId);
        originalFolder.setCreatorId(userId);
        ResourceFolderBO createdFolder = this.createFolder(originalFolder, conflictHandleEnum);
        if (conflictHandleEnum == ConflictModeEnum.RETURN_EXISTS && !Objects.equals(folderId, createdFolder.getFolderId()))
            return;

        List<ResourceFolderBO> subFolders = this.getSubFolders(originalOwnerId, originalCatalog, folderId, ResourceStatusEnum.NORMAL.code());
        List<ResourceFileBO> subFiles = this.getSubFiles(originalOwnerId, originalCatalog, folderId, ResourceStatusEnum.NORMAL.code(), null);
        List<String> subFileIds = subFiles.stream().map(ResourceFileBO::getFileId).collect(Collectors.toList());
        fileService.copyFilesInfo(subFileIds, createdFolder.getFolderId(), conflictHandleEnum);
        for (ResourceFolderBO subFolder : subFolders) {
            if (Objects.equals(createdFolder.getFolderId(), subFolder.getParentId()))
                continue;
            this.copyFolderInfo(subFolder.getFolderId(), createdFolder.getFolderId(), conflictHandleEnum);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void moveFoldersInfo(List<String> foldersId, String targetParentId, ConflictModeEnum conflictHandleEnum) {
        // 校验
        if (CollUtil.isEmpty(foldersId))
            return;
        // 去重
        foldersId = new ArrayList<>(new HashSet<>(foldersId));
        for (String folderId : foldersId) {
            ResourceFolderBO originalFolder = this.getFolder(folderId, ResourceStatusEnum.NORMAL.code(), true);
            if (Objects.equals(originalFolder.getParentId(), targetParentId) || Objects.equals(originalFolder.getFolderId(), targetParentId)) {
                return;
            }
            ResourceFolderBO folderInfo = new ResourceFolderBO()
                .setFolderId(folderId)
                .setParentId(targetParentId);
            this.updateFolderInfo(folderInfo, conflictHandleEnum, true);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateFolder(ResourceFolderBO folderBO, boolean throwDeleted) {
        if (folderBO == null)
            return;
        ResourceFolderBO folderExists = this.getFolder(folderBO.getFolderId(), true);
        // 更新目录信息
        this.updateFolderInfo(folderBO, ConflictModeEnum.THROW_EXISTS, throwDeleted);
        // 无状态变更，直接返回
        if (folderBO.getStatus() == null || Objects.equals(folderExists.getStatus(), folderBO.getStatus()))
            return;
        // 当有状态变更时
        this.updateFolderStatus(folderBO.getFolderId(), folderBO.getStatus(), throwDeleted);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateFolderStatus(String folderId, Integer status, boolean throwDeleted) {
        ResourceFolderBO folderBO = this.getFolder(folderId, -1, false, null, null, true);
        this.loopUpdateFoldersStatus(folderBO, status, throwDeleted);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateFoldersStatus(List<String> folderIdList, Integer status, boolean throwDeleted) {
        if (folderIdList == null)
            return;
        for (String folderId : folderIdList) {
            this.updateFolderStatus(folderId, status, throwDeleted);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateChildrenStatus(String parentFolderId, Integer status) {
        ResourceFolderBO folderBO = this.getFolder(parentFolderId, -1, false, null, null, true);
        if (CollectionUtil.isNotEmpty(folderBO.getSubFolders())) {
            for (ResourceFolderBO subFolder : folderBO.getSubFolders()) {
                this.loopUpdateFoldersStatus(subFolder, status, false);
            }
        }
        fileService.updateFilesStatusInFolder(parentFolderId, status);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateChildrenStatusInRoot(String ownerId, String catalog, Integer status) {
        ResourceFolderBO rootFolder = this.getRootFolder(ownerId, catalog, -1, false, null, null);
        if (CollectionUtil.isNotEmpty(rootFolder.getSubFolders())) {
            for (ResourceFolderBO subFolder : rootFolder.getSubFolders()) {
                this.loopUpdateFoldersStatus(subFolder, status, false);
            }
        }
        fileService.updateFilesStatusInRoot(ownerId, catalog, status);
    }

    @Override
    public boolean checkAuthOwn(String folderId, String ownerId) {
        Integer count = this.lambdaQuery().eq(ResourceFolder::getFolderId, folderId).eq(ResourceFolder::getOwnerId, ownerId).count();
        return ObjectUtil.compare(count, 0) > 0;
    }

    /**
     * 校验父目录是否存在
     * 当目录存在时，设定与目录相同的存储桶和所有者
     *
     * @param folderInfo 目录信息
     */
    private void verifyParentFolder(ResourceFolderBO folderInfo) {
        if (StringUtils.isNotBlank(folderInfo.getParentId()) && !StorageUtil.isRootFolder(folderInfo.getParentId(), false)) {
            ResourceFolderBO parentFolder = this.getFolder(folderInfo.getParentId(), true);
            AssertUtil.assertThrow("父目录不存在: " + folderInfo.getParentId(), parentFolder == null);
            AssertUtil.assertThrow("父目录已被删除：" + folderInfo.getParentId(), HttpStatus.GONE,
                ResourceStatusEnum.isArchived(parentFolder.getStatus()) || ResourceStatusEnum.isDeleted(parentFolder.getStatus()));
            folderInfo.setCatalog(parentFolder.getCatalog());
            folderInfo.setOwnerId(parentFolder.getOwnerId());
        }
    }

    /**
     * 校验同名约束
     *
     * @param folderInfo 目录信息
     */
    private ResourceFolderBO verifyFolderName(ResourceFolderBO folderInfo) {
        // 获取已存在的目录信息
        ResourceFolderBO folderOriginal = this.getFolder(folderInfo.getFolderId(), false);
        if (folderOriginal != null) {
            mapperFacade.map(folderInfo, folderOriginal);
        } else {
            folderOriginal = folderInfo;
        }
        // 忽略非NORMAL状态的资源
        if (!Objects.equals(folderOriginal.getStatus(), ResourceStatusEnum.NORMAL.code()))
            return null;
        // 校验同名文件约束
        ResourceFolderBO folderExist = this.getNormalFolderByName(
            folderOriginal.getOwnerId(), folderOriginal.getCatalog(), folderOriginal.getParentId(), folderOriginal.getName(), false);
        boolean conflict = folderExist != null && !Objects.equals(folderExist.getFolderId(), folderOriginal.getFolderId());
        if (conflict)
            return folderExist;
        return null;
    }


    /**
     * 目录创建参数校验
     */
    private void checkCreateParam(ResourceFolderBO folderInfo) {
        // 校验不可为空
        AssertUtil.assertThrow("父目录不可为空", StringUtils.isBlank(folderInfo.getParentId()));
        AssertUtil.assertThrow("目录名不可为空", StringUtils.isBlank(folderInfo.getName()));
        AssertUtil.assertThrow("catalog不可为空", StringUtils.isBlank(folderInfo.getCatalog()));
        // 校验父目录存在
        this.verifyParentFolder(folderInfo);
        // 校验不可为空
        AssertUtil.assertThrow("所有者不可为空", StringUtils.isBlank(folderInfo.getOwnerId()));
        // 设定默认值
        Date now = new Date();
        folderInfo.setFolderId(String.valueOf(idWorker.nextId()));
        folderInfo.setStatus(ResourceStatusEnum.NORMAL.code());
        folderInfo.setCreateDate(now);
        folderInfo.setUpdateDate(now);
        if (StringUtils.isBlank(folderInfo.getCreatorId()))
            folderInfo.setCreatorId(folderInfo.getOwnerId());
    }

    /**
     * 目录更新参数校验
     */
    private void checkUpdateParam(ResourceFolderBO folderInfo, boolean throwDeleted) {
        AssertUtil.assertThrow("目录ID为空", StringUtils.isBlank(folderInfo.getFolderId()));
        // 检查不可为空字段是否被更新为空
        if (StrUtil.isBlank(folderInfo.getName()))
            folderInfo.setName(null);
        if (folderInfo.getStatus() != null)
            ResourceStatusEnum.instance(folderInfo.getStatus(), true);
        if (StrUtil.isBlank(folderInfo.getCatalog()))
            folderInfo.setCatalog(null);
        if (StrUtil.isBlank(folderInfo.getCreatorId()))
            folderInfo.setCreatorId(null);
        if (StrUtil.isBlank(folderInfo.getOwnerId()))
            folderInfo.setOwnerId(null);
        // 设置更新时间
        folderInfo.setUpdateDate(new Date());

        ResourceFolderBO folderExist = this.getFolder(folderInfo.getFolderId(), true);
        // 防止更新已被删除的目录状态
        if (ResourceStatusEnum.isDeleted(folderExist.getStatus())) {
            AssertUtil.assertThrow("不可操作已删除目录", throwDeleted);
            folderInfo.setStatus(null);
        }
        // 校验父目录存在性
        if (StrUtil.isBlank(folderInfo.getParentId()))
            folderInfo.setParentId(folderExist.getParentId());
        this.verifyParentFolder(folderInfo);
    }

    private boolean isNameExists(String ownerId, String catalog, String parentId, String folderName, Integer status) {
        boolean isRoot = StorageUtil.isRootFolder(parentId, false);
        Integer count = this.lambdaQuery()
            .eq(isRoot, ResourceFolder::getOwnerId, ownerId)
            .eq(isRoot, ResourceFolder::getCatalog, catalog)
            .eq(ResourceFolder::getParentId, parentId)
            .eq(ResourceFolder::getName, folderName)
            .eq(status != null, ResourceFolder::getStatus, status).count();
        return !Objects.equals(count, 0);
    }

    /**
     * 根据目录名获取指定父目录下的目录信息
     * 注：只获取正常状态的目录
     *
     * @param ownerId     所有者ID
     * @param catalog     存储桶
     * @param parentId    父目录ID
     * @param folderName  目录名
     * @param checkExists 是否检查目录存在性
     * @return 待获取的目录
     */
    private ResourceFolderBO getNormalFolderByName(String ownerId, String catalog, String parentId, String folderName, boolean checkExists) {
        boolean isRoot = StorageUtil.isRootFolder(parentId, false);
        List<ResourceFolder> folderInfoList = this.lambdaQuery()
            .eq(isRoot, ResourceFolder::getOwnerId, ownerId)
            .eq(isRoot, ResourceFolder::getCatalog, catalog)
            .eq(ResourceFolder::getParentId, parentId)
            .eq(ResourceFolder::getName, folderName)
            .eq(ResourceFolder::getStatus, ResourceStatusEnum.NORMAL.code()).list();
        AssertUtil.assertThrow("目录不存在：" + folderName, checkExists && folderInfoList.size() == 0);
        if (folderInfoList.size() == 0)
            return null;
        return mapperFacade.map(folderInfoList.get(0), ResourceFolderBO.class);
    }

    /**
     * 获取目录下所有的子目录和子文件
     *
     * @param folderBO      当前目录信息,若为根目录，需提供ownerId和catalog
     * @param pathLevel     获取子目录的层级
     * @param withFile      是否获取文件
     * @param status        目录/文件状态
     * @param fileIntegrity 文件完整性
     */
    private void loopGetChildren(ResourceFolderBO folderBO, int pathLevel, boolean withFile, Integer status, Integer fileIntegrity) {
        if (folderBO == null || pathLevel-- == 0) {
            return;
        }
        List<ResourceFolderBO> subFolders = this.getSubFolders(folderBO.getOwnerId(), folderBO.getCatalog(), folderBO.getFolderId(), status);
        folderBO.setSubFolders(subFolders);
        if (withFile) {
            List<ResourceFileBO> subFiles = this.getSubFiles(folderBO.getOwnerId(), folderBO.getCatalog(), folderBO.getFolderId(), status, fileIntegrity);
            folderBO.setSubFiles(subFiles);
        }
        for (ResourceFolderBO subFolder : subFolders) {
            this.loopGetChildren(subFolder, pathLevel, withFile, status, fileIntegrity);
        }
    }

    private List<ResourceFolderBO> getSubFolders(String ownerId, String catalog, String folderId, Integer status) {
        ResourceStatusEnum subInstance = ResourceStatusEnum.subInstance(status, false);
        Integer subStatus = subInstance == null ? null : subInstance.code();
        boolean isRoot = StorageUtil.isRootFolder(folderId, false);
        List<ResourceFolder> subFolders = this.lambdaQuery()
            .eq(isRoot, ResourceFolder::getOwnerId, ownerId)
            .eq(isRoot, ResourceFolder::getCatalog, catalog)
            .eq(ResourceFolder::getParentId, folderId)
            .and(status != null, i -> i.eq(ResourceFolder::getStatus, status)
                .or().eq(ResourceFolder::getStatus, subStatus))
            .orderByDesc(ResourceFolder::getUpdateDate)
            .list();
        return mapperFacade.mapAsList(subFolders, ResourceFolderBO.class);
    }

    private List<ResourceFileBO> getSubFiles(String ownerId, String catalog, String folderId, Integer status, Integer fileIntegrity) {
        boolean isRoot = StorageUtil.isRootFolder(folderId, false);
        return isRoot ? fileService.listFileInfoInRoot(ownerId, catalog, status, fileIntegrity) : fileService.listFileInfoInFolder(folderId, status, fileIntegrity);
    }

    /**
     * 更新目录持久化信息
     * <p>
     * 目录更新校验：
     * 1. 目录ID不为空
     * 2. 不可为空字段检查
     * 3. 防止更新“已删除”状态
     * 4. 若更新父目录，验证父目录存在且状态正常
     * 5. 若更新目录名，验证父目录下无正常状态的同名目录
     */
    private void updateFolderInfo(ResourceFolderBO folderBO, ConflictModeEnum conflictHandleEnum, boolean throwDeleted) {
        this.checkUpdateParam(folderBO, throwDeleted);
        ResourceFolderBO folderExist = this.verifyFolderName(folderBO);
        // 移动目录且存在同名目录时
        if (folderExist != null) {
            switch (conflictHandleEnum) {
                case THROW_EXISTS:
                    throw FolderConflictException.build("存在同名目录", new ResourceFolderConflictBO(folderBO, folderExist));
                case MERGE_EXISTS:
                    log.debug("移动目标目录已存在，合并目录：{}和{}", folderBO.getFolderId(), folderExist.getFolderId());
                    List<ResourceFolderBO> subFolders = this.getSubFolders(folderBO.getOwnerId(), folderBO.getCatalog(), folderBO.getFolderId(), ResourceStatusEnum.NORMAL.code());
                    List<ResourceFileBO> subFiles = this.getSubFiles(folderBO.getOwnerId(), folderBO.getCatalog(), folderBO.getFolderId(), ResourceStatusEnum.NORMAL.code(), null);
                    List<String> subFolderIds = subFolders.stream().map(ResourceFolderBO::getFolderId).collect(Collectors.toList());
                    List<String> subFileIds = subFiles.stream().map(ResourceFileBO::getFileId).collect(Collectors.toList());
                    this.moveFoldersInfo(subFolderIds, folderExist.getFolderId(), ConflictModeEnum.MERGE_EXISTS);
                    fileService.moveFilesInfo(subFileIds, folderExist.getFolderId(), ConflictModeEnum.MERGE_EXISTS);
                    this.updateFolderStatus(folderBO.getFolderId(), ResourceStatusEnum.DELETED_COVER.code(), false);
                    return;
                case RETURN_EXISTS:
                    this.updateFolderStatus(folderBO.getFolderId(), ResourceStatusEnum.DELETED_COVER.code(), false);
                    return;
                case COVER_EXISTS:
                    this.updateFolderStatus(folderExist.getFolderId(), ResourceStatusEnum.DELETED_COVER.code(), false);
                    break;
                case RENAME_ORIGINAL:
                    String rename = this.getNextCopyName(folderExist.getOwnerId(), folderExist.getCatalog(), folderExist.getParentId(), folderExist.getName());
                    log.debug("待创建目录已存在，重命名目录：" + rename);
                    folderBO.setName(rename);
                    break;
            }
        }
        ResourceFolder folderDO = mapperFacade.map(folderBO, ResourceFolder.class);
        this.updateById(folderDO);
    }

    /**
     * 更新目录状态，持久层操作
     */
    private void updateFolderInfoStatus(String folderId, Integer status, boolean throwDeleted) {
        if (StringUtils.isBlank(folderId))
            return;
        ResourceStatusEnum.instance(status, true);
        ResourceFolderBO folderExist = this.getFolder(folderId, true);
        // 状态无变化
        if (Objects.equals(folderExist.getStatus(), status))
            return;
        // 目录已被删除
        if (ResourceStatusEnum.isDeleted(folderExist.getStatus()) && !ResourceStatusEnum.isDeleted(status)) {
            AssertUtil.assertThrow("不可操作已删除目录", throwDeleted);
            return;
        }
        this.lambdaUpdate().set(ResourceFolder::getStatus, status)
            .set(ResourceFolder::getUpdateDate, new Date())
            .eq(ResourceFolder::getFolderId, folderId).update();
    }

    /**
     * 根据目录及其所有子目录和文件状态
     *
     * @param folderBO 待更新的目录信息
     * @param status   待更新状态
     */
    private void loopUpdateFoldersStatus(ResourceFolderBO folderBO, Integer status, boolean throwDeleted) {
        if (folderBO == null) return;
        // 限制对根目录的更新
        StorageUtil.isRootFolder(folderBO.getFolderId(), true);
        // 检查status
        ResourceStatusEnum.instance(status, true);
        // 获取subStatus
        ResourceStatusEnum subStatus = ResourceStatusEnum.subInstance(status, true);

        // 更新目录状态
        this.updateFolderInfoStatus(folderBO.getFolderId(), status, throwDeleted);
        // 更新子文件状态
        fileService.updateFilesStatusInFolder(folderBO.getFolderId(), subStatus.code());
        // 更新子目录状态
        for (ResourceFolderBO subFolder : folderBO.getSubFolders()) {
            this.loopUpdateFoldersStatus(subFolder, subStatus.code(), throwDeleted);
        }
    }

    private String getNextCopyName(String ownerId, String catalog, String parentId, String folderName) {
        String nameSuffixHead = folderName + StorageConstants.COPY_SUFFIX_HEAD;
        boolean isRoot = StorageUtil.isRootFolder(parentId, false);
        List<ResourceFolder> folderInfoList = this.lambdaQuery()
            .eq(isRoot, ResourceFolder::getOwnerId, ownerId)
            .eq(isRoot, ResourceFolder::getCatalog, catalog)
            .eq(ResourceFolder::getParentId, parentId)
            .eq(ResourceFolder::getStatus, ResourceStatusEnum.NORMAL.code())
            .likeRight(ResourceFolder::getName, nameSuffixHead)
            .orderByDesc(ResourceFolder::getName).list();
        for (ResourceFolder folderInfo : folderInfoList) {
            String name = folderInfo.getName();
            String indexStr = name.substring(nameSuffixHead.length(), name.lastIndexOf(StorageConstants.COPY_SUFFIX_END));
            try {
                int index = Integer.parseInt(indexStr) + 1;
                return nameSuffixHead + index + StorageConstants.COPY_SUFFIX_END;
            } catch (NumberFormatException e) {
                continue;
            }
        }
        return nameSuffixHead + 1 + StorageConstants.COPY_SUFFIX_END;
    }
}
